2008年06月20日
川俣晶の縁側ソフトウェア技術雑記 total 11207 count

デリゲート型フィールドをラムダ式で初期化する際のメンバ参照可能・不可能の違い

Written By: 川俣 晶連絡先

 ちょっと悩ましい問題に気付いたのでメモっておきます。

 対象はC# 3.0です。

 メモなので用語等不適切かもしれません。

デリゲート型フィールドをラムダ式で初期化する §

 リスト1は通るコードです。

 コンストラクタでデリゲート型フィールドをラムダ式で初期化しています。

リスト1 コンストラクタで初期化 §

using System;

class A

{

    private int x = 0;

    public Action d;

    public A()

    {

        d = () =>

            {

                Console.WriteLine(x);

            };

    }

}

class Program

{

    static void Main(string[] args)

    {

        A a = new A();

        a.d();

    }

}

初期化式を移動させる §

 ラムダ式をコンストラクタからフィールドを直接初期化する位置に移動させると、コンパイルが通らなくなります。

リスト2 フィールドを直接初期化 §

using System;

class A

{

    private int x = 0;

    public Action d = () =>

    {

        Console.WriteLine(x);

    };

}

class Program

{

    static void Main(string[] args)

    {

        A a = new A();

        a.d();

    }

}

エラー 1 フィールド初期化子は、静的でないフィールド、メソッド、またはプロパティ 'A.x' を参照できません d:\w\test\ConsoleApplication73\ConsoleApplication73\Program.cs 8 27 ConsoleApplication73

何が問題なのか §

 リスト1はthisをキャプチャしているからメンバにアクセスでき、リスト2はキャプチャすべきthisが無いからメンバにアクセスできない、という話ではないようです。Reflectorで見る限り、キャプチャという手順無しでthisへのアクセスは可能になっているようです。(デリゲート型はインスタンスの情報を持つので、それを経由すれば上位環境のthisをラムダ式に引き継げる……のだと思う。たぶん)

 つまり、初期化タイミングが違うだけで、メンバにアクセスできる、できないの差が出ます。

別の例 §

 オブジェクトの初期化構文を使っても、やはり初期化はできませんが、理由は違います。このラムダ式はクラスBではなくクラスCに含まれるので、そもそもBに対するthisはあり得ません。

リスト3 オブジェクトの初期化構文を使う §

using System;

class B

{

    public int x = 0;

    public Action SomeMethod;

}

class C

{

    public B b = new B()

        {

            SomeMethod = () =>

                {

                    Console.WriteLine(x);

                },

        };

}

class Program

{

    static void Main(string[] args)

    {

        C c = new C();

        c.b.SomeMethod();

    }

}

エラー 1 エラー 1 名前 'x' は現在のコンテキスト内に存在しません。 d:\w\test\ConsoleApplication75\ConsoleApplication75\Program.cs 18 39 ConsoleApplication75

で、何が問題なの? §

 当面は以上の確認とメモだけ。